package networkinterface;

import java.io.File;
import java.net.DatagramPacket;
import java.util.Observable;

import chatController.ChatController;


import messages.*;
import model.SharingFiles;
import model.SharingMessages;
import model.State;
import model.SystemState;
import model.User;
import model.Users;

/**
 * @author Michael & Pierre
 *
 */
/**
 * Network Interface of the system
 */
public class JavaNI extends ChatNI {
	
	/**
	 * Thread listening the network continually 
	 */
	private ListeningThread myListeningThread;
	
	/**
	 * @param chatController Controller of the chatSystem
	 * @param users model for users management
	 * @param sharingMessages model for sharing chat messages management
	 * @param sharingFiles model for sharing files management
	 * @param state model for system state management
	 */
	public JavaNI(ChatController chatController, Users users, SharingMessages sharingMessages, 
					SharingFiles sharingFiles, SystemState state) {
		super(chatController, users, sharingMessages, sharingFiles, state);
	}
	
	
	/**
	 * Do what the NI has to do according to the model's changes
	 * @param o model which has launched the update
	 * @param arg object given by the model
	 */
	public void update(Observable o, Object arg) {
		if (o == users) {
			if (arg instanceof User) {
				if ((User)arg == users.getLocalUser()) {
	            	myListeningThread = new ListeningThread(this, LISTEN_PORT);
	            	myListeningThread.start();
					sendHello (BROADCAST, true);
				}
				else {
					sendHello (((User)arg).getHost(), false);
				}
			}
		}
		else if (o == sharingMessages) {
			if (sharingMessages.isToSend()) {
				sendMsg(sharingMessages.getUser(), sharingMessages.getText());
			}
		}
		else if (o == sharingFiles) {
			if (sharingFiles.getWaitRemoteAnswer()) {
				if (sharingFiles.isToSend() && !sharingFiles.isSent() && !sharingFiles.isAccepted()) {
					sendAskFile(sharingFiles.getUser(), sharingFiles.getFileToSend());
				}
				else if ((!sharingFiles.isToSend()) && (!sharingFiles.isSent())) {
					if (sharingFiles.isAccepted()) {
                        chatController.setFileSent(true);
						recvFile(sharingFiles.getFileName(), sharingFiles.getUser(), sharingFiles.getDestDirectory());
					}
					else {
						refuseFile(sharingFiles.getFileName(), sharingFiles.getUser());
						chatController.setFileSent(true);
					}
				}
			}
			else {
				if (sharingFiles.isToSend() && !sharingFiles.isSent() && sharingFiles.isAccepted()) {
					sendFile(sharingFiles.getFileToSend(), sharingFiles.getUser().getHost());
				}
			}
		}
		else if (o == state) {
			if (arg == State.disconnecting) {
				close();
			}
		}
	}
		
	/**
	 * Called when a packet is received by the listening thread
	 * @param myPacket Datagram packet received
	 */
	protected void packetRcvd(DatagramPacket myPacket) {
		
		try {
			
			Message recvMessage = MessagesFactory.getMessage(myPacket.getData());
			
			if (recvMessage instanceof Hello){
				chatController.helloRcvd(recvMessage.getUsername(), recvMessage.getHostName());
			}
			else if (recvMessage instanceof Bye){
				chatController.byeRcvd(recvMessage.getUsername(), recvMessage.getHostName());
			}
			else if (recvMessage instanceof ChatMessage) {
				chatController.messageRcvd(((ChatMessage)recvMessage).getMsgToSend(), recvMessage.getUsername(), recvMessage.getHostName());
			}
			else if (recvMessage instanceof FileNotif){
				
				if (((FileNotif) recvMessage).getRequest() == true) {
					chatController.fileNotifRcvd(((FileNotif)recvMessage).getNameFile(), recvMessage.getUsername(), recvMessage.getHostName());
				}
				else {
					chatController.remoteResponseRcvd(((FileNotif) recvMessage).getAccepted());
				}
			}
			
		} catch (MessageCreationException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * Send a hello message to a given address
	 * @param dest address name of recipient, can be a broadcast address. 
	 * @param waitAck false if the hello is an answer to another hello 
	 */
	protected void sendHello (String dest, boolean waitAck) {
		try {
			
			SendingThread monThread = new SendingThread(MessagesFactory.getHello(users.getLocalUser().getUsername(),
									waitAck), dest, PORT);
			monThread.start();
			
			chatController.helloSent();
			
		} catch (MessageCreationException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * Send a message to a given user
	 * @param destUser recipient of the message
	 * @param message 
	 */
	protected void sendMsg(User destUser, String message) {
		try {

			SendingThread monThread = new SendingThread(MessagesFactory.getChatMessage(users.getLocalUser().getUsername(), 
														message), destUser.getHost(), PORT);
			monThread.start();

		} catch (MessageCreationException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * Ask a given user if he wants to receive a given file
	 * @param destUser recipient of the file
	 * @param myFile
	 */
	protected void sendAskFile(User destUser, File myFile) {
		try {
			
			SendingThread monThread = new SendingThread(MessagesFactory.getFileNotif(users.getLocalUser().getUsername(), 
					myFile.getName(), true, false, myFile.length()), destUser.getHost(), PORT);
			monThread.start();
			
		} catch (MessageCreationException e) {
			e.printStackTrace();
		}		
	}
	
	/**
	 * Send a file to a given host
	 * @param file
	 * @param host
	 */
	protected void sendFile(File file, String host) {
		SendFileThread sendFileThread = new SendFileThread(file, host, FILE_PORT, this);
		sendFileThread.start();
	}
	
	/**
	 * Called when a file has been sent
	 */
	protected void fileSent() {
		chatController.fileSent();
	}
	
	/**
	 * Let a remote user know that the local user accepted his file and receive it
	 * @param fileName
	 * @param user
	 * @param destDirectory directory where the received file will be stored
	 */
	protected void recvFile(String fileName, User user, String destDirectory) {
		try {
			SendingThread monThread = new SendingThread(MessagesFactory.getFileNotif(users.getLocalUser().getUsername(), 
							fileName, false, true, 0), user.getHost(), PORT);
			monThread.start();
			
			ReceiveFileThread recvFileThread = new ReceiveFileThread((destDirectory + "/" + fileName), FILE_RECVPORT, this);
			recvFileThread.start();
		} catch (MessageCreationException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * Called when a file has been received
	 */
	protected void fileRcvd() {
		chatController.fileRcvd();
	}
	
	/**
	 * Notify a remote user that the local user doesn't want to receive his file
	 * @param fileName
	 * @param user remote user who asked to send a file
	 */
	public void refuseFile(String fileName, User user) {
		try {
			SendingThread monThread = new SendingThread(MessagesFactory.getFileNotif(users.getLocalUser().getUsername(), 
					fileName, false, false, 0), user.getHost(), PORT);
			monThread.start();
		} catch (MessageCreationException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * Send a bye message to the broadcast address
	 */
	protected void sendBye() { 
		try {

			SendingThread monThread = new SendingThread(MessagesFactory.getBye(users.getLocalUser().getUsername()), 
														BROADCAST, PORT);
			monThread.start();
			
			chatController.byeSent();
		} catch (MessageCreationException e) {
			e.printStackTrace();
		}
	}

	/**
	 * Closes the listening thread and calls sendBye()
	 */
	protected void close() { 
		myListeningThread.interrupt();
		sendBye();
	}

}
